- OC方法调用的实质
- 动态绑定和消息转发的流程
- 动态绑定实例(添加实例方法、添加类方法)
- 消息转发实例
- 消息转发模拟“多继承”以及二者的区别
- 使用消息转发代替继承
OC方法调用的实质
我们都知道,OC是C语言的超集,赋予了C强大的动态特性,其功劳主要归功于OC的运行时系统(runtime)。OC调用方法的实质是发送消息(即objc_msgSend函数)。在编译期,对象理论上可以调用任何方法,即使此方法并没有实现。直到运行期,系统才会根据方法名(SEL)去class的数据结构中查找对应方法进行方法调用。
动态绑定和消息转发的流程
给对象(类的实例)发送消息(即调用方法)的大致流程如下:
- 根据实例的isa指针确定所属class
- 在class的缓存表中查看是否存在此方法。若存在,加入缓存表后,找到对应IMP直接调用
- 不存在缓存时,在class的方法表中查找。若存在,加入缓存表后,找到对应IMP直接调用
- 方法表不存在时,根据class的super_class指针,在父类的方法表中继续查找。若存在,加入缓存表后,找到对应IMP直接调用
- 都没有查找到时,runtime会启动动态绑定,调用resolveInstanceMethod方法,此时,可以动态添加此方法到class中。如已添加,加入缓存表后,找到对应IMP直接调用
- 若没有动态添加方法,runtime会启动快速消息转发机制,即调用forwardTargetForSelector方法。我们可以直接返回一个类实例,指定其为调用者的代理对象,直接执行其对应的方法
- 如果没有指定代理对象,runtime会启动完整的消息转发机制(forwardInvocation):首先,调用methodSignatureForSelector方法,返回一个包含此方法实例对象的方法签名;接着,系统会根据此方法签名,生成一个NSInvacation对象(包含着方法选择器等信息),带有此参数并调用forwardInvocation方法。我们在forwardInvocation中,使用invacation对象调用invokeWithTarget方法,传入需要转发的实例对象。即完成了消息转发,系统会自动调用转发对象的对应方法,并将结果返回给最初的调用者。
- 如果我们没有把消息转发给任何对象,最后系统会调用doesNotRecognizeMethod方法,并在其内部抛出异常,即表示无法处理此消息,最终默认crash掉APP。
以上步骤中,1~4为方法的正常调用流程,5为动态绑定流程,6~8为消息转发流程。
转发一张动态绑定和消息转发的流程图(上面的步骤5~8):
动态绑定实例
- 添加实例方法
|
|
- 添加类方法:
我们知道,与实例方法不同,类方法是保存在类所属元类的方法表中(即class -> isa -> meta class -> method list)。所以给类动态绑定类方法,需要绑定到元类中,通过resolveClassMethod进行添加:
|
|
消息转发实例
这里只列举了实例方法的消息转发。对于类方法的消息转发,只需将对应的“-”改为“+”即可。
|
|
消息转发模拟“多继承”以及二者的区别
面向对象编程,其实根本目的就是为了代码复用。面向对象的方式之一就是继承,但由于OC只支持单继承,一定程度上减少了很多代码复用的机会,而且并不是所有的类都可以直接继承(如OC的类簇)。而消息转发机制可以在一定程度上解决这个问题。
个人理解,消息转发实际上与代理模式一样,调用者本身不具备指定功能,通过代理对象调用方法来间接实现自己的功能。外部看起来与本身直接调用一样。当把多个功能分别转发给多个对象后,调用者本身就具备了多个类的多种功能,看起来就像是多继承一样。这也就赋予了OC“多继承”的功能,而且实现了代码复用(功能复用)。
但是消息转发和真正的多继承还是存在明显区别的:
- 多继承是“多合一”:多个父类的功能组合生成子类,子类的功能及结构会变多且复杂。
- 消息转发是“一分多”:通过代理对象,将多个功能分发到多个类中进行实现,本类的实际结构和功能则相对简单,且对外部透明。
使用消息转发代替继承
虽然消息转发隐藏了细节,在外部看起来像是调用类存在某种功能(实现了这个方法),或者是你认为调用类“继承”了转发类。但实际上,通过诸如respondsToSelector、isKindOfClass等函数还是可以识别出真相(此类函数只会查看本身的继承链,不会查看转发链)。如果你想要完全模拟“继承”,则需要覆盖此类方法才行。
官方文档有说过,一定要慎用消息转发,在没有彻底弄清转发机制和本类及转发类的功能时,不要乱用这个机制。
参考内容: